// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "window_manager/layout2/browser_window.h"

#include <map>
#include <string>

#include "base/time.h"
#include "cros/chromeos_wm_ipc_enums.h"
#include "window_manager/atom_cache.h"
#include "window_manager/callback.h"
#include "window_manager/event_consumer_registrar.h"
#include "window_manager/event_loop.h"
#include "window_manager/layout2/layout_manager2.h"
#include "window_manager/shadow.h"
#include "window_manager/stacking_manager.h"
#include "window_manager/transient_window_collection.h"
#include "window_manager/window.h"
#include "window_manager/window_manager.h"

using base::TimeDelta;
using std::map;
using std::string;

namespace {

// Distance over which we move the window for the no-op window-switching
// animation.
const int kNudgeAnimPixels = 25;

// Amount of time used for the no-op window-switching animation.
const int kNudgeAnimMs = 180;

// Scale of the browser window in the first and second keyframe of the no-op
// "squish" animation displayed when the user attempts to toggle to overlapping
// mode while there's only a single browser window open.
const double kSquishAnimShrinkScale = 0.975;
const double kSquishAnimReboundScale = 1.0125;

// Total amount of time used for the no-op "squish" animation.
const int kSquishAnimMs = 300;

}  // anonymous namespace

namespace window_manager {

BrowserWindow::BrowserWindow(Window* win,
                             LayoutManager2* layout_manager,
                             int unmaximized_width)
    : win_(win),
      event_consumer_registrar_(
          new EventConsumerRegistrar(wm(), layout_manager)),
      unmaximized_width_(unmaximized_width),
      unmaximized_anchoring_(ANCHOR_LEFT),
      status_area_displayed_(
          !win->chrome_state_xatoms().count(ATOM_CHROME_STATE_STATUS_HIDDEN)),
      brightness_(1.0),
      transients_(
          new TransientWindowCollection(
              win,  // owner_win
              TransientWindowCollection::STACK_IN_LAYER,
              TransientWindowCollection::CENTER_OVER_OWNER,
              TransientWindowCollection::KEEP_ONSCREEN_IF_OWNER_IS_ONSCREEN,
              layout_manager)),
      dimming_box_(
          wm()->compositor()->CreateColoredBox(
              win_->client_width(), win_->client_height(),
              Compositor::Color("#000"))) {
  DCHECK(ShouldHandleWindow(*win));
  wm()->focus_manager()->UseClickToFocusForWindow(
      win, FocusManager::PASS_CLICKS_THROUGH);
  event_consumer_registrar_->RegisterForWindowEvents(win->xid());

  win_->SetVisibility(Window::VISIBILITY_SHOWN);
  win_->SetShadowType(Shadow::TYPE_RECTANGULAR);

  dimming_box_->SetName(string("dimming box for window ") + win_->xid_str());
  dimming_box_->Move(win_->client_x(), win_->client_y(), 0);
  dimming_box_->SetOpacity(0, 0);
  wm()->stage()->AddActor(dimming_box_.get());
  wm()->stacking_manager()->StackActorRelativeToOtherActor(
      dimming_box_.get(),
      win_->GetTopActor(),
      StackingManager::ABOVE_SIBLING);
}

BrowserWindow::~BrowserWindow() {
  win_->SetVisibility(Window::VISIBILITY_HIDDEN);
  transients_->CloseAllWindows();
  win_ = NULL;
}

// static
bool BrowserWindow::ShouldHandleWindow(const Window& win) {
  if (win.transient_for_xid())
    return false;

  return win.type() == chromeos::WM_IPC_WINDOW_CHROME_TOPLEVEL ||
         win.type() == chromeos::WM_IPC_WINDOW_UNKNOWN;
}

WindowManager* BrowserWindow::wm() const {
  return win_->wm();
}

void BrowserWindow::SetBounds(const Rect& bounds, int anim_ms) {
  win_->SetBounds(bounds, anim_ms);
  dimming_box_->Move(bounds.x, bounds.y, anim_ms);
  dimming_box_->SetSize(bounds.width, bounds.height);
  transients_->ConfigureAllWindowsRelativeToOwner(anim_ms);
}

void BrowserWindow::StackAtTopOfLayer(
    StackingManager::Layer layer,
    StackingManager::ShadowPolicy shadow_policy,
    StackingManager::Layer shadow_layer) {
  wm()->stacking_manager()->StackWindowAtTopOfLayer(
      win_, layer, shadow_policy, shadow_layer);
  wm()->stacking_manager()->StackActorRelativeToOtherActor(
      dimming_box_.get(),
      win_->GetTopActor(),
      StackingManager::ABOVE_SIBLING);

  transients_->set_stacking_policy(
      layer == StackingManager::LAYER_FULLSCREEN_WINDOW ?
      TransientWindowCollection::STACK_ABOVE_OWNER :
      TransientWindowCollection::STACK_IN_LAYER);
  transients_->ApplyStackingForAllWindows();
}

void BrowserWindow::StackBelowOtherBrowserWindow(
    BrowserWindow* other_browser,
    StackingManager::ShadowPolicy shadow_policy,
    StackingManager::Layer shadow_layer) {
  DCHECK(other_browser);
  // The layer that we pass here is irrelevant since we're stacking our shadow
  // directly below our actor rather than at the bottom of the layer.
  wm()->stacking_manager()->StackWindowRelativeToOtherWindow(
      win_,
      other_browser->win(),
      StackingManager::BELOW_SIBLING,
      shadow_policy,
      shadow_layer);
  wm()->stacking_manager()->StackActorRelativeToOtherActor(
      dimming_box_.get(),
      win_->GetTopActor(),
      StackingManager::ABOVE_SIBLING);
  transients_->set_stacking_policy(TransientWindowCollection::STACK_IN_LAYER);
  transients_->ApplyStackingForAllWindows();
}

void BrowserWindow::TakeFocus(XTime timestamp) {
  if (!transients_->TakeFocus(timestamp))
    wm()->FocusWindow(win_, timestamp);
}

void BrowserWindow::HandleTransientWindowMap(Window* transient_win) {
  if (!transient_win->is_rgba())
    transient_win->SetShadowType(Shadow::TYPE_RECTANGULAR);
  transients_->AddWindow(transient_win);
}

void BrowserWindow::HandleTransientWindowUnmap(Window* transient_win) {
  transients_->RemoveWindow(transient_win);
}

void BrowserWindow::HandleTransientWindowConfigureRequest(
    Window* transient_win, const Rect& requested_bounds) {
  transients_->HandleConfigureRequest(transient_win, requested_bounds);
}

bool BrowserWindow::IsWindowOrTransientFocused() const {
  return win_->IsFocused() || transients_->HasFocusedWindow();
}

void BrowserWindow::SetPreferredTransientWindowToFocus(Window* transient_win) {
  transients_->SetPreferredWindowToFocus(transient_win);
}

void BrowserWindow::HandleTransientWindowModalityChange(Window* transient_win) {
  transients_->HandleWindowModalityChange(transient_win);
}

void BrowserWindow::SetTransientWindowVisibility(bool shown) {
  if (shown)
    transients_->Show();
  else
    transients_->Hide();
}

void BrowserWindow::DoNudgeAnimation(bool move_to_left) {
  AnimationPair* animations = win_->CreateMoveCompositedAnimation();
  animations->AppendKeyframe(
      win_->composited_x() + (move_to_left ? -1 : 1) * kNudgeAnimPixels,
      win_->composited_y(),
      TimeDelta::FromMilliseconds(kNudgeAnimMs / 2));
  animations->AppendKeyframe(
      win_->composited_x(), win_->composited_y(),
      TimeDelta::FromMilliseconds(kNudgeAnimMs / 2));
  win_->SetMoveCompositedAnimation(animations);
}

void BrowserWindow::DoSquishAnimation() {
  const TimeDelta frame_time = TimeDelta::FromMilliseconds(kSquishAnimMs / 3);

  AnimationPair* animations = win_->CreateScaleCompositedAnimation();
  animations->AppendKeyframe(kSquishAnimShrinkScale, 1.0, frame_time);
  animations->AppendKeyframe(kSquishAnimReboundScale, 1.0, frame_time);
  animations->AppendKeyframe(1.0, 1.0, frame_time);
  win_->SetScaleCompositedAnimation(animations);

  const Rect bounds = win_->client_bounds();
  animations = win_->CreateMoveCompositedAnimation();
  animations->AppendKeyframe(
      bounds.x + 0.5 * (1.0 - kSquishAnimShrinkScale) * bounds.width, bounds.y,
      frame_time);
  animations->AppendKeyframe(
      bounds.x + 0.5 * (1.0 - kSquishAnimReboundScale) * bounds.width, bounds.y,
      frame_time);
  animations->AppendKeyframe(bounds.x, bounds.y, frame_time);
  win_->SetMoveCompositedAnimation(animations);
}

void BrowserWindow::SetBrightness(double brightness, int anim_ms) {
  DCHECK_GE(brightness, 0.0);
  DCHECK_LE(brightness, 1.0);
  brightness_ = brightness;
  dimming_box_->SetOpacity(1.0 - brightness, anim_ms);
}

double BrowserWindow::GetInstantaneousBrightness() const {
  return 1.0 - dimming_box_->GetOpacity();
}

void BrowserWindow::SetStatusAreaDisplayed(bool displayed) {
  if (status_area_displayed_ == displayed ||
      win_->type() != chromeos::WM_IPC_WINDOW_CHROME_TOPLEVEL)
    return;

  status_area_displayed_ = displayed;
  map<XAtom, bool> states;
  states[wm()->GetXAtom(ATOM_CHROME_STATE_STATUS_HIDDEN)] = !displayed;
  win_->ChangeChromeState(states);
}

void BrowserWindow::SetFullscreenProperty(bool fullscreen) {
  if (win_->wm_state_fullscreen() == fullscreen)
    return;

  map<XAtom, bool> states;
  states[wm()->GetXAtom(ATOM_NET_WM_STATE_FULLSCREEN)] = fullscreen;
  win_->ChangeWmState(states);
}

void BrowserWindow::SetMaximizedProperty(bool maximized) {
  if (win_->wm_state_maximized_horz() == maximized &&
      win_->wm_state_maximized_vert() == maximized)
    return;

  map<XAtom, bool> states;
  if (win_->wm_state_maximized_horz() != maximized)
    states[wm()->GetXAtom(ATOM_NET_WM_STATE_MAXIMIZED_HORZ)] = maximized;
  if (win_->wm_state_maximized_vert() != maximized)
    states[wm()->GetXAtom(ATOM_NET_WM_STATE_MAXIMIZED_VERT)] = maximized;
  win_->ChangeWmState(states);
}

}  // namespace window_manager
